import logst.parsers_base as parsers_base
import re

class dmapper_Parser(parsers_base.base_Parser_class):

   default_icon = "images/32px-Crystal_Clear_app_kcmdf.png"

   def initialize(self):

      # (disk, host, channel, id, lun)
      self.disks = []

      self.add_option("scsi_hints", "Provide hints such as disk to SCSI mappings")

   def sos_handler(self, sos):

      # let's fetch mount information
     for line in sos.open_and_readlines("mount"):

         line = line.strip()

         (dev, _on, mountpoint, _t, xtype, opts) = line.split(None, 5)

         self.add_hint( '%s' % mountpoint, re.escape(line) )

   def init_activators(self):

      self.add_regex_activator("""SCSI (disk )error.*return code""", self.parse_scsi_error)

      if self.get_option("scsi_hints").is_true():

         self.add_regex_activator("""Attached (scsi|SCSI) disk""", self.parse_attached_disk)

      return

   def parse_scsi_error(self, log):

      for regex in [ r"""^SCSI error : <(?P<scsi_id>.+)> return code = (?P<retcode>.+)$""", r"""^sd (?P<scsi_id>.+): SCSI error: return code = (?P<retcore>.+)$""", r"""^SCSI disk error : host (?P<host>.+) channel (?P<chan>.+) id (?P<id>.+) lun (?P<lun>.+) return code = (?P<retcode>.+)$""" ]:

         found = re.match( regex, log.message() )

         if found:
            break

      else:
         return

      ret = found.groupdict()

      if not ret.has_key("scsi_id") and ret["host"] and ret["chan"] and ret["id"] and ret["lun"]:
         ret["scsi_id"] = "%s:%s:%s:%s" % ( ret["host"], ret["chan"], ret["id"], ret["lun"] )

      if ret["retcode"].startswith("0x"):
         ret["retcode"] = ret["retcode"][2:]

      self.add_event(log, "SCSI error on %s" % ret["scsi_id"])

      self.add_hint(regex = "return code = (0x|)%s" % ret["retcode"], hint_txt = self._get_scsi_error(ret["retcode"]), pre_regex = " ", post_regex = "$" )

      return

   # These parsers store our SCSI disks layout to generate hints

   def parse_attached_disk(self, logline):

      for regex in [ r"""^sd (?P<host>.+):(?P<chan>.+):(?P<id>.+):(?P<lun>.+): Attached scsi disk (?P<disk>.+)$""", r"""^sd (?P<host>.+):(?P<chan>.+):(?P<id>.+):(?P<lun>.+): \[(?P<disk>.+)\] Attached SCSI disk$""", r"""^Attached scsi disk (?P<disk>.+) at scsi(?P<host>.+), channel (?P<chan>.+), id (?P<id>.+), lun (?P<lun>.+)$""" ]:

         found = re.match( regex, logline.message() )

         if found:
            break

      else:
         return

      ret = found.groupdict()

      scsi_id = "%s:%s:%s:%s" % (ret["host"], ret["chan"], ret["id"], ret["lun"])

      self.add_hint(ret["disk"], "SCSI %s" % scsi_id, hostnames = [logline.hostname()], post_regex = "[^a-zA-Z]")

      self.add_hint(scsi_id, "Disk %s" % ret["disk"], hostnames = [logline.hostname()] )

      # we can learn a little about the layout
      self.disks.append( (scsi_id, ret["disk"]) )

   def parse_line(self, date, logline):

      found = re.findall(r"""^end_request: I/O error, dev (.*), sector .*$""", logline.message())
      if found:
         self.add_event(logline, "I/O error on %s" % (found[0]), self.default_css)
         return

      if logline.daemon() != "multipathd":
         return

      found = re.findall(r"""(.*): mark as failed$""", logline.message())
      if found:
         disk = self._get_disk_from_majmin(found[0])
         self.add_event(logline, "Multipath path %s (possibly %s) failed" % (found[0], disk), self.default_css)
         return

      found = re.findall(r"""(.*): reinstated$""", logline.message())
      if found:
         disk = self._get_disk_from_majmin(found[0])
         self.add_event(logline, "Multipath path %s (%s) reinstated" % (found[0], disk), self.default_css)
         return

      return

   def _get_disk_from_majmin(self, majmin):
      major, minor = majmin.split(":")
      major, minor = int(major), int(minor)

      block_majors = [8, 65, 66, 67, 68, 69, 70, 71, 128, 129, 130, 131, 132, 133, 134, 135]

      disk = (block_majors.index(major) * 16) + int(minor / 16)
      partition = minor % 16

      # 97 = ord('a')
      # 25 = ord('z') - ord('a')

      rchar = chr(97 + (disk % 25))

      if disk > 25:
         lchar = chr(97 - 1 + int(disk / 25))
         rchar = chr(ord(rchar)-1)
      else:
         lchar = ""

      return "sd" + lchar + rchar

   def _get_msg_error(self, hexerr):
      err = (((hexerr) >> 8) & 0xff)

      if err == 0x00: return
      if err == 0x01: return "EXTENDED_MESSAGE"
      if err == 0x02: return "SAVE_POINTERS"
      if err == 0x03: return "RESTORE_POINTERS"
      if err == 0x04: return "DISCONNECT"
      if err == 0x05: return "INITIATOR_ERROR"
      if err == 0x06: return "ABORT"
      if err == 0x07: return "MESSAGE_REJECT"
      if err == 0x08: return "NOP"
      if err == 0x09: return "MSG_PARITY_ERROR"
      if err == 0x0a: return "LINKED_CMD_COMPLETE"
      if err == 0x0b: return "LINKED_FLG_CMD_COMPLETE"
      if err == 0x0c: return "BUS_DEVICE_RESET"
      if err == 0x0f: return "INITIATE_RECOVERY"
      if err == 0x10: return "RELEASE_RECOVERY"
      if err == 0x20: return "SIMPLE_QUEUE_TAG"
      if err == 0x21: return "HEAD_OF_QUEUE_TAG"
      if err == 0x22: return "ORDERED_QUEUE_TAG"

      return "Unknown (%s)" % (hexerr)

   def _get_host_error(self, hexerr):
      err = (((hexerr) >> 16) & 0xff)

      if err == 0x00: return 
      if err == 0x01: return "DID_NO_CONNECT (Couldn't connect before timeout period)"
      if err == 0x02: return "DID_BUS_BUSY (BUS stayed busy through time out period)"
      if err == 0x03: return "DID_TIME_OUT (TIMED OUT for other reason)"
      if err == 0x04: return "DID_BAD_TARGET (BAD target)"
      if err == 0x05: return "DID_ABORT (Told to abort for some other reason)"
      if err == 0x06: return "DID_PARITY (Parity error)"
      if err == 0x07: return "DID_ERROR (Internal error)"
      if err == 0x08: return "DID_RESET (Reset by somebody)"
      if err == 0x09: return "DID_BAD_INTR (Got an interrupt we weren't expecting)"
      if err == 0x0a: return "DID_PASSTHROUGH (Force command past mid-layer)"
      if err == 0x0b: return "DID_SOFT_ERROR (The low level driver just wish a retry)"
      if err == 0x0c: return "DID_IMM_RETRY (Retry without decrementing retry count)"
      if err == 0x0d: return "DID_REQUEUE (Requeue command (no immediate retry) also without decrementing the retry count)"
      if err == 0x0e: return "DID_COND_REQUEUE (Requeue cmd w/o decr of retry count IFF not blk_noretry_request)"

      return "Unknown (%s)" % (hexerr)

   def _get_driver_error(self, hexerr):
      err = (((hexerr) >> 24) & 0xff)

      if err == 0x00: return
      if err == 0x01: return "DRIVER_BUSY"
      if err == 0x02: return "DRIVER_SOFT"
      if err == 0x03: return "DRIVER_MEDIA"
      if err == 0x04: return "DRIVER_ERROR"
      if err == 0x05: return "DRIVER_INVALID"
      if err == 0x06: return "DRIVER_TIMEOUT"
      if err == 0x07: return "DRIVER_HARD"
      if err == 0x08: return "DRIVER_SENSE"

      return "Unknown (%s)" % (hexerr)

   def _get_scsi_error(self, hexerr):

      hexerr = int(hexerr, 16)
      errmsg = ""

      e = self._get_msg_error(hexerr)
      if e: errmsg += " msg_byte: %s" % e

      e = self._get_driver_error(hexerr)
      if e: errmsg += " driver_byte: %s" % e

      e = self._get_host_error(hexerr)
      if e: errmsg += " host_byte: %s" % e

      return errmsg.strip()
